from wormbane.config.config import default_grid_size
from wormbane.geometry import direction_to_vector, Rectangle, Vector
from wormbane.model.boulder import Boulder
from wormbane.model.monster import Monster
from wormbane.model.feature import (Altar, Corridor, Door, Doorway, Floor,
                                    Stair, Wall)
from wormbane.model.room import Room
from wormbane.model.level import Level
from wormbane.model.races.dragon import Dragon
from wormbane.model.races.drow import Drow
from wormbane.model.races.elf import Elf
from wormbane.model.races.goblin import Goblin
from wormbane.model.races.hobbit import Hobbit
from wormbane.model.races.hydra import Hydra
from wormbane.model.races.lindworm import Lindworm
from wormbane.model.races.titan import Titan
from wormbane.model.races.troll import Troll
from wormbane.model.races.wyvern import Wyvern
from wormbane.util.a_star_search import a_star_search
from wormbane.util.random_ import weighted_choice, percent_chance

import random

def generate_level(level_num, size = default_grid_size):
    level = Level(size)
    level.level_num = level_num
    _generate_rooms(level)
    _generate_altar(level)
    _generate_stairs(level)
    _generate_corridors(level)
    _generate_monsters(level)
    return level

def _generate_rooms(level):
    for i in xrange(100):
        _generate_room(level)

def _generate_room(level):
    bounds = _generate_room_bounds(level)
    if not _validate_room_bounds(bounds, level):
        return None
    room = Room(bounds)
    level.rooms.append(room)
    _paint_room(room, level)
    return room

def _generate_room_bounds(level):
    room_width = 5 + random.randint(0, 7) + random.randint(0, 7)
    room_height = 4 + random.randint(0, 2) + random.randint(0, 2)
    level_width, level_height = level.size
    x = random.randint(0, level_width - room_width)
    y = random.randint(0, level_height - room_height)
    return Rectangle((x, y), (x + room_width, y + room_height))

def _validate_room_bounds(bounds, level):
    padded_bounds = _grow_rectangle(bounds, (3, 3))
    return not any(padded_bounds.intersects(room.bounds)
                   for room in level.rooms)

def _grow_rectangle(rect, padding):
    dx, dy = padding
    return Rectangle((rect.min_x - dx, rect.min_y - dy),
                     (rect.max_x + dx, rect.max_y + dy))

def _paint_room(room, level):
    min_x = room.bounds.min_x
    min_y = room.bounds.min_y
    max_x = room.bounds.max_x
    max_y = room.bounds.max_y
    _fill_rectangle((min_x, min_y), (max_x, min_y + 1),
                    lambda: Wall(axis = 'x'), level)
    _fill_rectangle((min_x, max_y - 1), (max_x, max_y),
                    lambda: Wall(axis = 'x'), level)
    _fill_rectangle((min_x, min_y + 1), (min_x + 1, max_y - 1),
                    lambda: Wall(axis = 'y'), level)
    _fill_rectangle((max_x - 1, min_y + 1), (max_x, max_y - 1),
                    lambda: Wall(axis = 'y'), level)
    _fill_rectangle((min_x + 1, min_y + 1), (max_x - 1, max_y - 1), Floor,
                    level)

def _generate_altar(level):
    _generate_feature_in_room(Altar(), random.choice(level.rooms[1:-1]), level)

def _generate_stairs(level):
    level.stair_up_pos = _generate_feature_in_room(Stair(direction = 'up'),
                                                   level.rooms[0], level)
    level.stair_down_pos = _generate_feature_in_room(Stair(direction = 'down'),
                                                     level.rooms[-1], level)

def _fill_rectangle(min_point, max_point, create_feature, level):
    min_x, min_y = min_point
    max_x, max_y = max_point
    for x in xrange(min_x, max_x):
        for y in xrange(min_y, max_y):
            create_feature().move_to(level.cells[Vector(x, y)])

def _pick_point_in_room(room):
    x = random.randrange(room.bounds.min_x + 1, room.bounds.max_x - 1)
    y = random.randrange(room.bounds.min_y + 1, room.bounds.max_y - 1)
    return Vector(x, y)

def _generate_feature_in_room(feature, room, level):
    pos = _pick_point_in_room(room)
    feature.move_to(level.cells[pos])
    return pos

def _generate_corridors(level):
    if len(level.rooms) < 2:
        return
    connected = set()
    unconnected = set(level.rooms)

    def connect(from_rooms, to_rooms):
        to_connect = _nearest_rooms(from_rooms, to_rooms)
        from_room, to_room = to_connect
        unconnected.discard(from_room)
        unconnected.discard(to_room)
        connected.update(to_connect)
        _generate_corridor(from_room, to_room, level)

    connect(unconnected, unconnected)
    while unconnected:
        connect(unconnected, connected)

def _generate_corridor(room_a, room_b, level):
    start = _pick_point_in_room(room_a)
    goal = _pick_point_in_room(room_b)
    offsets = [direction_to_vector(dir)
               for dir in ('north', 'west', 'east', 'south')]

    def predicate(pos):
        return pos == goal or level.cells[pos].feature == 'corridor'

    def is_room_wall(pos):
        feature = level.cells[pos].feature
        return feature.is_wall and feature.axis or feature.is_door

    def validate_neighbor(pos, neighbor_pos):
        if neighbor_pos not in level.bounds:
            return False
        if is_room_wall(pos) and is_room_wall(neighbor_pos):
            return False
        if (is_room_wall(neighbor_pos)
            and neighbor_pos not in room_a.bounds
            and neighbor_pos not in room_b.bounds):
            return False
        return True

    def neighbors(pos):
        return (pos + offset for offset in offsets
                if validate_neighbor(pos, pos + offset))
    
    def cost(pos_a, pos_b):
        return 1
    
    def heuristic(pos):
        pos_x, pos_y = pos
        goal_x, goal_y = goal
        return abs(pos_x - goal_x) + abs(pos_y - goal_y)
    
    path, nodes = a_star_search(start, predicate, neighbors, cost, heuristic)
    for node in path:
        cell = level.cells[node.pos]
        if cell.feature.is_wall:
            if cell.feature.axis is None:
                if percent_chance(5):
                    cell.feature.secret = True
                else:
                    Corridor().move_to(cell)
            else:
                if percent_chance(20):
                    cell.feature.secret = True
                else:
                    Door(axis = cell.feature.axis).move_to(cell)

def _nearest_rooms(rooms_a, rooms_b):
    best_rooms = None
    best_dist = None
    for room_a in rooms_a:
        for room_b in rooms_b:
            if room_a != room_b:
                dist = room_a.bounds.distance_to(room_b.bounds)
                if best_dist is None or dist < best_dist:
                    best_rooms = room_a, room_b
                    best_dist = dist
    return best_rooms

_races = (Dragon, Drow, Elf, Goblin, Hobbit, Hydra, Lindworm, Titan, Troll,
          Wyvern)

def _generate_monsters(level):
    for i in xrange(random.randint(10, 20)):
        room = random.choice(level.rooms)
        pos = _pick_point_in_room(room)
        cell = level.cells[pos]
        if not cell.monster and not cell.items and cell.feature.is_floor:
            race = weighted_choice((race, race.frequency) for race in _races)
            race.create_equipped_monster().move_to(cell)
